"""Install Manager for atomic system updates.

Provides atomic installation with rollback support, service management,
and comprehensive progress reporting.
"""

import os
import json
import asyncio
import shutil
import tarfile
import zipfile
import tempfile
import logging
import subprocess
import time
from pathlib import Path
from typing import Optional, Dict, List, Callable, Any, Union
from dataclasses import dataclass, asdict
from enum import Enum
from datetime import datetime
import hashlib
import sys

# Add project root to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent.parent))

from src.services.failure_detector import FailureDetector, FailureEvent, DiagnosticReport, HealthStatus


logger = logging.getLogger(__name__)


class InstallState(Enum):
    """Installation state enumeration."""
    IDLE = "IDLE"
    VALIDATING = "VALIDATING"
    BACKUP_CREATING = "BACKUP_CREATING"
    SERVICE_STOPPING = "SERVICE_STOPPING"
    INSTALLING = "INSTALLING"
    CONFIGURING = "CONFIGURING"
    SERVICE_STARTING = "SERVICE_STARTING"
    VALIDATING_INSTALL = "VALIDATING_INSTALL"
    COMPLETE = "COMPLETE"
    FAILED = "FAILED"
    ROLLING_BACK = "ROLLING_BACK"
    RESTORED = "RESTORED"
    ROLLBACK_FAILED = "ROLLBACK_FAILED"
    MONITORING = "MONITORING"


class RollbackTrigger(Enum):
    """Rollback trigger reasons."""
    MANUAL = "MANUAL"
    AUTO_SERVICE_FAILURE = "AUTO_SERVICE_FAILURE"
    AUTO_HEALTH_CHECK_FAILURE = "AUTO_HEALTH_CHECK_FAILURE"
    AUTO_RESOURCE_EXHAUSTION = "AUTO_RESOURCE_EXHAUSTION"
    AUTO_FILE_CORRUPTION = "AUTO_FILE_CORRUPTION"
    AUTO_CONNECTIVITY_LOSS = "AUTO_CONNECTIVITY_LOSS"


class InstallError(Exception):
    """Installation-specific error."""
    pass


@dataclass
class InstallProgress:
    """Installation progress information."""
    state: InstallState
    stage: str
    progress_percent: float
    current_step: str
    total_steps: int
    elapsed_seconds: float
    estimated_time_seconds: Optional[int]
    error_message: Optional[str] = None
    
    @property
    def estimated_time_formatted(self) -> str:
        """Get formatted estimated time string."""
        if self.estimated_time_seconds is None:
            return "Unknown"
        
        minutes = self.estimated_time_seconds // 60
        seconds = self.estimated_time_seconds % 60
        return f"{minutes:02d}:{seconds:02d}"


@dataclass
class PackageInfo:
    """Update package information."""
    name: str
    version: str
    build: str
    description: str
    file_path: str
    size_bytes: int
    checksum: Optional[str] = None
    
    def to_dict(self) -> Dict:
        """Convert to dictionary."""
        return asdict(self)


@dataclass
class InstallManifest:
    """Installation manifest from install.json."""
    package: Dict[str, str]
    dependencies: Dict[str, Any]
    install: Dict[str, Any]
    rollback: Dict[str, Any]
    
    @classmethod
    def from_dict(cls, data: Dict) -> 'InstallManifest':
        """Create from dictionary."""
        return cls(
            package=data.get('package', {}),
            dependencies=data.get('dependencies', {}),
            install=data.get('install', {}),
            rollback=data.get('rollback', {})
        )


class ServiceManager:
    """System service management."""
    
    @staticmethod
    async def stop_service(service_name: str) -> bool:
        """Stop a systemd service."""
        try:
            cmd = ["systemctl", "stop", service_name]
            result = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            await result.communicate()
            return result.returncode == 0
        except Exception as e:
            logger.error(f"Failed to stop service {service_name}: {e}")
            return False
    
    @staticmethod
    async def start_service(service_name: str) -> bool:
        """Start a systemd service."""
        try:
            cmd = ["systemctl", "start", service_name]
            result = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            await result.communicate()
            return result.returncode == 0
        except Exception as e:
            logger.error(f"Failed to start service {service_name}: {e}")
            return False
    
    @staticmethod
    async def is_service_active(service_name: str) -> bool:
        """Check if service is active."""
        try:
            cmd = ["systemctl", "is-active", service_name]
            result = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, _ = await result.communicate()
            return stdout.decode().strip() == "active"
        except Exception as e:
            logger.error(f"Failed to check service status {service_name}: {e}")
            return False
    
    @staticmethod
    async def reload_daemon() -> bool:
        """Reload systemd daemon."""
        try:
            cmd = ["systemctl", "daemon-reload"]
            result = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            await result.communicate()
            return result.returncode == 0
        except Exception as e:
            logger.error(f"Failed to reload daemon: {e}")
            return False


class PackageExtractor:
    """Update package extraction and validation."""
    
    @staticmethod
    def extract_package(package_path: str, extract_dir: str) -> bool:
        """Extract update package to directory."""
        try:
            package_path = Path(package_path)
            extract_dir = Path(extract_dir)
            
            if not package_path.exists():
                raise InstallError(f"Package not found: {package_path}")
            
            extract_dir.mkdir(parents=True, exist_ok=True)
            
            if package_path.suffix.lower() == '.zip':
                with zipfile.ZipFile(package_path, 'r') as zip_file:
                    zip_file.extractall(extract_dir)
            elif package_path.name.endswith('.tar.gz') or package_path.suffix == '.tgz':
                with tarfile.open(package_path, 'r:gz') as tar_file:
                    tar_file.extractall(extract_dir)
            else:
                raise InstallError(f"Unsupported package format: {package_path.suffix}")
            
            return True
            
        except Exception as e:
            logger.error(f"Failed to extract package: {e}")
            return False
    
    @staticmethod
    def load_manifest(extract_dir: str) -> Optional[InstallManifest]:
        """Load installation manifest from extracted package."""
        try:
            manifest_path = Path(extract_dir) / "install.json"
            if not manifest_path.exists():
                logger.warning("install.json not found in package")
                return None
            
            with open(manifest_path, 'r') as f:
                manifest_data = json.load(f)
            
            return InstallManifest.from_dict(manifest_data)
            
        except Exception as e:
            logger.error(f"Failed to load manifest: {e}")
            return None
    
    @staticmethod
    def validate_package_integrity(extract_dir: str, manifest: InstallManifest) -> bool:
        """Validate package file integrity."""
        try:
            # Check if all required files exist
            install_files = manifest.install.get('files', [])
            for file_info in install_files:
                source_path = Path(extract_dir) / file_info['source']
                if not source_path.exists():
                    logger.error(f"Missing file in package: {file_info['source']}")
                    return False
            
            # Verify checksum file if present
            checksum_path = Path(extract_dir) / "metadata" / "checksum.txt"
            if checksum_path.exists():
                return PackageExtractor._verify_checksums(extract_dir, checksum_path)
            
            return True
            
        except Exception as e:
            logger.error(f"Package integrity validation failed: {e}")
            return False
    
    @staticmethod
    def _verify_checksums(extract_dir: str, checksum_path: Path) -> bool:
        """Verify file checksums."""
        try:
            with open(checksum_path, 'r') as f:
                for line in f:
                    line = line.strip()
                    if not line or line.startswith('#'):
                        continue
                    
                    parts = line.split('  ', 1)
                    if len(parts) != 2:
                        continue
                    
                    expected_hash, relative_path = parts
                    file_path = Path(extract_dir) / relative_path
                    
                    if not file_path.exists():
                        logger.error(f"Checksum file missing: {relative_path}")
                        return False
                    
                    # Calculate SHA256
                    actual_hash = PackageExtractor._calculate_file_hash(file_path)
                    if actual_hash != expected_hash:
                        logger.error(f"Checksum mismatch for {relative_path}")
                        return False
            
            return True
            
        except Exception as e:
            logger.error(f"Checksum verification failed: {e}")
            return False
    
    @staticmethod
    def _calculate_file_hash(file_path: Path) -> str:
        """Calculate SHA256 hash of file."""
        hasher = hashlib.sha256()
        with open(file_path, 'rb') as f:
            for chunk in iter(lambda: f.read(8192), b""):
                hasher.update(chunk)
        return hasher.hexdigest()


class BackupManager:
    """System backup management."""
    
    def __init__(self, backup_dir: str = "/var/backups/cloud-printer"):
        self.backup_dir = Path(backup_dir)
        self.backup_dir.mkdir(parents=True, exist_ok=True)
    
    def create_backup(self, version: str, files_to_backup: List[str]) -> Optional[str]:
        """Create backup of current system files."""
        try:
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            backup_name = f"backup_{version}_{timestamp}"
            backup_path = self.backup_dir / backup_name
            backup_path.mkdir(parents=True, exist_ok=True)
            
            backup_manifest = {
                "version": version,
                "timestamp": timestamp,
                "files": []
            }
            
            for file_path in files_to_backup:
                source = Path(file_path)
                if source.exists():
                    # Preserve directory structure
                    relative_path = source.relative_to(source.anchor)
                    target = backup_path / relative_path
                    target.parent.mkdir(parents=True, exist_ok=True)
                    
                    if source.is_file():
                        shutil.copy2(source, target)
                    elif source.is_dir():
                        shutil.copytree(source, target, dirs_exist_ok=True)
                    
                    backup_manifest["files"].append({
                        "source": str(source),
                        "target": str(target),
                        "mode": oct(source.stat().st_mode)[-3:] if source.exists() else "644"
                    })
                    
                    logger.info(f"Backed up: {source} -> {target}")
            
            # Save backup manifest
            manifest_path = backup_path / "backup_manifest.json"
            with open(manifest_path, 'w') as f:
                json.dump(backup_manifest, f, indent=2)
            
            logger.info(f"Backup created: {backup_path}")
            return str(backup_path)
            
        except Exception as e:
            logger.error(f"Failed to create backup: {e}")
            return None
    
    def restore_backup(self, backup_path: str) -> bool:
        """Restore from backup."""
        try:
            backup_path = Path(backup_path)
            manifest_path = backup_path / "backup_manifest.json"
            
            if not manifest_path.exists():
                raise InstallError(f"Backup manifest not found: {manifest_path}")
            
            with open(manifest_path, 'r') as f:
                manifest = json.load(f)
            
            for file_info in manifest["files"]:
                source = Path(file_info["target"])
                target = Path(file_info["source"])
                
                if source.exists():
                    target.parent.mkdir(parents=True, exist_ok=True)
                    
                    if source.is_file():
                        shutil.copy2(source, target)
                    elif source.is_dir():
                        if target.exists():
                            shutil.rmtree(target)
                        shutil.copytree(source, target)
                    
                    # Restore permissions
                    mode = int(file_info["mode"], 8)
                    target.chmod(mode)
                    
                    logger.info(f"Restored: {source} -> {target}")
            
            logger.info(f"Restore completed from: {backup_path}")
            return True
            
        except Exception as e:
            logger.error(f"Failed to restore backup: {e}")
            return False
    
    def cleanup_old_backups(self, keep_count: int = 3):
        """Clean up old backups, keeping only the most recent ones."""
        try:
            # Get all backup directories
            backup_dirs = [d for d in self.backup_dir.iterdir() 
                          if d.is_dir() and d.name.startswith("backup_")]
            
            # Sort by modification time (newest first)
            backup_dirs.sort(key=lambda x: x.stat().st_mtime, reverse=True)
            
            # Remove old backups
            for old_backup in backup_dirs[keep_count:]:
                shutil.rmtree(old_backup)
                logger.info(f"Deleted old backup: {old_backup}")
                
        except Exception as e:
            logger.error(f"Failed to cleanup old backups: {e}")


class InstallManager:
    """Manages atomic system installation with rollback support."""
    
    def __init__(self, 
                 backup_dir: str = "/var/backups/cloud-printer",
                 install_timeout: int = 600,  # 10 minutes
                 enable_auto_rollback: bool = True,
                 post_install_monitoring_duration: int = 300):  # 5 minutes
        """Initialize install manager.
        
        Args:
            backup_dir: Directory for system backups
            install_timeout: Installation timeout in seconds
            enable_auto_rollback: Enable automatic rollback on failure
            post_install_monitoring_duration: Duration to monitor after install (seconds)
        """
        self.backup_manager = BackupManager(backup_dir)
        self.service_manager = ServiceManager()
        self.package_extractor = PackageExtractor()
        self.install_timeout = install_timeout
        self.enable_auto_rollback = enable_auto_rollback
        self.post_install_monitoring_duration = post_install_monitoring_duration
        
        # Installation state
        self.current_state = InstallState.IDLE
        self.current_progress = 0
        self.current_step = ""
        self.total_steps = 0
        self.start_time: Optional[float] = None
        self.error_message: Optional[str] = None
        
        # Installation context
        self.current_package: Optional[PackageInfo] = None
        self.current_manifest: Optional[InstallManifest] = None
        self.extract_dir: Optional[str] = None
        self.backup_path: Optional[str] = None
        self.progress_callback: Optional[Callable] = None
        
        # Stopped services tracking
        self.stopped_services: List[str] = []
        
        # Failure detection and rollback
        self.failure_detector: Optional[FailureDetector] = None
        self._monitoring_task: Optional[asyncio.Task] = None
        self.rollback_history: List[Dict] = []
        self.last_rollback_trigger: Optional[RollbackTrigger] = None
        self.rollback_callback: Optional[Callable] = None
    
    def set_progress_callback(self, callback: Callable[[InstallProgress], None]):
        """Set progress callback function."""
        self.progress_callback = callback
    
    async def _update_progress(self, state: InstallState, stage: str, 
                              step: str, progress: float):
        """Update installation progress."""
        self.current_state = state
        self.current_step = step
        
        elapsed = time.time() - self.start_time if self.start_time else 0
        
        # Estimate remaining time based on progress
        if progress > 0:
            total_estimated = elapsed / (progress / 100)
            remaining = max(0, total_estimated - elapsed)
        else:
            remaining = None
        
        progress_info = InstallProgress(
            state=state,
            stage=stage,
            progress_percent=progress,
            current_step=step,
            total_steps=self.total_steps,
            elapsed_seconds=elapsed,
            estimated_time_seconds=int(remaining) if remaining else None,
            error_message=self.error_message
        )
        
        if self.progress_callback:
            try:
                await self.progress_callback(progress_info)
            except Exception as e:
                logger.error(f"Progress callback error: {e}")
        
        logger.info(f"Install progress: {stage} - {step} ({progress:.1f}%)")
    
    async def _pre_install_checks(self, package_path: str) -> bool:
        """Perform pre-installation checks."""
        try:
            await self._update_progress(
                InstallState.VALIDATING, "VALIDATION", 
                "Checking package file", 5
            )
            
            package_path = Path(package_path)
            if not package_path.exists():
                raise InstallError(f"Package file not found: {package_path}")
            
            # Check available disk space
            await self._update_progress(
                InstallState.VALIDATING, "VALIDATION",
                "Checking disk space", 10
            )
            
            stat = shutil.disk_usage(package_path.parent)
            available_gb = stat.free / (1024 ** 3)
            package_size_gb = package_path.stat().st_size / (1024 ** 3)
            
            # Need at least 3x package size for extraction, backup, and installation
            if available_gb < package_size_gb * 3:
                raise InstallError(
                    f"Insufficient disk space. Available: {available_gb:.1f}GB, "
                    f"Required: {package_size_gb * 3:.1f}GB"
                )
            
            return True
            
        except Exception as e:
            self.error_message = str(e)
            logger.error(f"Pre-install checks failed: {e}")
            return False
    
    async def _extract_and_validate_package(self, package_path: str) -> bool:
        """Extract and validate update package."""
        try:
            await self._update_progress(
                InstallState.VALIDATING, "VALIDATION",
                "Extracting update package", 15
            )
            
            # Create temporary extraction directory
            self.extract_dir = tempfile.mkdtemp(prefix="install_")
            
            # Extract package
            if not self.package_extractor.extract_package(package_path, self.extract_dir):
                raise InstallError("Failed to extract package")
            
            await self._update_progress(
                InstallState.VALIDATING, "VALIDATION",
                "Loading installation manifest", 20
            )
            
            # Load manifest
            self.current_manifest = self.package_extractor.load_manifest(self.extract_dir)
            if not self.current_manifest:
                raise InstallError("Invalid package: missing installation manifest")
            
            await self._update_progress(
                InstallState.VALIDATING, "VALIDATION",
                "Validating package integrity", 25
            )
            
            # Validate package integrity
            if not self.package_extractor.validate_package_integrity(
                self.extract_dir, self.current_manifest
            ):
                raise InstallError("Package integrity validation failed")
            
            # Create package info
            package_info = self.current_manifest.package
            self.current_package = PackageInfo(
                name=package_info.get('name', 'unknown'),
                version=package_info.get('version', '0.0.0'),
                build=package_info.get('build', ''),
                description=package_info.get('description', ''),
                file_path=package_path,
                size_bytes=Path(package_path).stat().st_size
            )
            
            return True
            
        except Exception as e:
            self.error_message = str(e)
            logger.error(f"Package extraction/validation failed: {e}")
            return False
    
    async def _create_system_backup(self) -> bool:
        """Create backup of current system."""
        try:
            await self._update_progress(
                InstallState.BACKUP_CREATING, "BACKUP",
                "Creating system backup", 30
            )
            
            if not self.current_manifest:
                return False
            
            # Get files to backup
            files_to_backup = []
            for file_info in self.current_manifest.install.get('files', []):
                if file_info.get('backup', False):
                    files_to_backup.append(file_info['target'])
            
            # Add critical files from rollback config
            critical_files = self.current_manifest.rollback.get('critical_files', [])
            files_to_backup.extend(critical_files)
            
            # Create backup
            current_version = "current"  # Would get from version manager
            self.backup_path = self.backup_manager.create_backup(
                current_version, files_to_backup
            )
            
            if not self.backup_path:
                raise InstallError("Failed to create system backup")
            
            await self._update_progress(
                InstallState.BACKUP_CREATING, "BACKUP",
                "Backup created successfully", 35
            )
            
            return True
            
        except Exception as e:
            self.error_message = str(e)
            logger.error(f"Backup creation failed: {e}")
            return False
    
    async def _stop_services(self) -> bool:
        """Stop required services."""
        try:
            if not self.current_manifest:
                return False
            
            services_to_stop = self.current_manifest.install.get('services_to_stop', [])
            
            for i, service in enumerate(services_to_stop):
                await self._update_progress(
                    InstallState.SERVICE_STOPPING, "SERVICE_CONTROL",
                    f"Stopping {service}", 40 + (i * 5)
                )
                
                if await self.service_manager.stop_service(service):
                    self.stopped_services.append(service)
                    logger.info(f"Stopped service: {service}")
                else:
                    logger.warning(f"Failed to stop service: {service}")
            
            # Wait a moment for services to fully stop
            await asyncio.sleep(2)
            
            return True
            
        except Exception as e:
            self.error_message = str(e)
            logger.error(f"Service stopping failed: {e}")
            return False
    
    async def _install_files(self) -> bool:
        """Install files atomically."""
        try:
            if not self.current_manifest or not self.extract_dir:
                return False
            
            files_to_install = self.current_manifest.install.get('files', [])
            
            for i, file_info in enumerate(files_to_install):
                progress = 50 + (i * 20 / len(files_to_install))
                await self._update_progress(
                    InstallState.INSTALLING, "FILE_INSTALLATION",
                    f"Installing {file_info['source']}", progress
                )
                
                source_path = Path(self.extract_dir) / file_info['source']
                target_path = Path(file_info['target'])
                
                if not source_path.exists():
                    raise InstallError(f"Source file not found: {source_path}")
                
                # Create target directory
                target_path.parent.mkdir(parents=True, exist_ok=True)
                
                # Use atomic installation (copy to temp then move)
                temp_target = target_path.with_suffix(target_path.suffix + '.tmp')
                
                try:
                    shutil.copy2(source_path, temp_target)
                    
                    # Set permissions
                    mode = int(file_info.get('mode', '644'), 8)
                    temp_target.chmod(mode)
                    
                    # Atomic move
                    temp_target.rename(target_path)
                    
                    logger.info(f"Installed: {source_path} -> {target_path}")
                    
                except Exception as e:
                    # Cleanup temp file
                    if temp_target.exists():
                        temp_target.unlink()
                    raise e
            
            return True
            
        except Exception as e:
            self.error_message = str(e)
            logger.error(f"File installation failed: {e}")
            return False
    
    async def _configure_system(self) -> bool:
        """Configure system after installation."""
        try:
            await self._update_progress(
                InstallState.CONFIGURING, "CONFIGURATION",
                "Updating system configuration", 70
            )
            
            if not self.current_manifest:
                return False
            
            # Process configuration templates
            config_templates = self.current_manifest.install.get('config_templates', [])
            for config in config_templates:
                await self._update_progress(
                    InstallState.CONFIGURING, "CONFIGURATION",
                    f"Processing {config['source']}", 75
                )
                
                # Handle configuration file merging/updating
                # This is simplified - real implementation would handle merge strategies
                source_path = Path(self.extract_dir) / config['source']
                target_path = Path(config['target'])
                
                if source_path.exists() and config.get('merge_strategy') != 'preserve_user':
                    target_path.parent.mkdir(parents=True, exist_ok=True)
                    shutil.copy2(source_path, target_path)
                    logger.info(f"Updated config: {target_path}")
            
            # Reload systemd if service files were updated
            await self._update_progress(
                InstallState.CONFIGURING, "CONFIGURATION",
                "Reloading system daemon", 78
            )
            
            await self.service_manager.reload_daemon()
            
            return True
            
        except Exception as e:
            self.error_message = str(e)
            logger.error(f"System configuration failed: {e}")
            return False
    
    async def _start_services(self) -> bool:
        """Start services after installation."""
        try:
            if not self.current_manifest:
                return False
            
            services_to_start = self.current_manifest.install.get('services_to_start', [])
            
            for i, service in enumerate(services_to_start):
                progress = 80 + (i * 10 / len(services_to_start))
                await self._update_progress(
                    InstallState.SERVICE_STARTING, "SERVICE_CONTROL",
                    f"Starting {service}", progress
                )
                
                if await self.service_manager.start_service(service):
                    logger.info(f"Started service: {service}")
                else:
                    logger.warning(f"Failed to start service: {service}")
                    # Continue with other services
            
            return True
            
        except Exception as e:
            self.error_message = str(e)
            logger.error(f"Service starting failed: {e}")
            return False
    
    async def _validate_installation(self) -> bool:
        """Validate installation completed successfully."""
        try:
            await self._update_progress(
                InstallState.VALIDATING_INSTALL, "VALIDATION",
                "Validating installation", 90
            )
            
            if not self.current_manifest:
                return False
            
            # Check that critical files exist and are executable
            critical_files = self.current_manifest.rollback.get('critical_files', [])
            for critical_file in critical_files:
                file_path = Path(critical_file)
                if not file_path.exists():
                    raise InstallError(f"Critical file missing after install: {critical_file}")
            
            # Check that services are running
            services_to_start = self.current_manifest.install.get('services_to_start', [])
            failed_services = []
            
            for service in services_to_start:
                if not await self.service_manager.is_service_active(service):
                    failed_services.append(service)
            
            if failed_services:
                logger.warning(f"Services not active after install: {failed_services}")
                # Not necessarily a failure - services might take time to start
            
            await self._update_progress(
                InstallState.VALIDATING_INSTALL, "VALIDATION",
                "Installation validation complete", 95
            )
            
            return True
            
        except Exception as e:
            self.error_message = str(e)
            logger.error(f"Installation validation failed: {e}")
            return False
    
    async def _rollback_installation(self) -> bool:
        """Rollback failed installation."""
        try:
            await self._update_progress(
                InstallState.ROLLING_BACK, "ROLLBACK",
                "Rolling back installation", 10
            )
            
            if not self.backup_path:
                logger.error("No backup available for rollback")
                return False
            
            # Stop services that were started
            if self.current_manifest:
                services_to_stop = self.current_manifest.install.get('services_to_start', [])
                for service in services_to_stop:
                    await self.service_manager.stop_service(service)
            
            await self._update_progress(
                InstallState.ROLLING_BACK, "ROLLBACK",
                "Restoring files from backup", 50
            )
            
            # Restore from backup
            if not self.backup_manager.restore_backup(self.backup_path):
                raise InstallError("Failed to restore from backup")
            
            await self._update_progress(
                InstallState.ROLLING_BACK, "ROLLBACK",
                "Restarting original services", 80
            )
            
            # Restart originally stopped services
            for service in self.stopped_services:
                await self.service_manager.start_service(service)
            
            await self._update_progress(
                InstallState.RESTORED, "ROLLBACK",
                "Rollback completed successfully", 100
            )
            
            return True
            
        except Exception as e:
            self.error_message = str(e)
            logger.error(f"Rollback failed: {e}")
            await self._update_progress(
                InstallState.ROLLBACK_FAILED, "ROLLBACK",
                f"Rollback failed: {e}", 100
            )
            return False
    
    def _cleanup_temp_files(self):
        """Clean up temporary files."""
        try:
            if self.extract_dir and Path(self.extract_dir).exists():
                shutil.rmtree(self.extract_dir)
                logger.info(f"Cleaned up temporary directory: {self.extract_dir}")
        except Exception as e:
            logger.error(f"Failed to cleanup temp files: {e}")
    
    async def install_package(self, package_path: str, force: bool = False) -> bool:
        """Install update package atomically.
        
        Args:
            package_path: Path to update package file
            force: Force installation even if validation warnings
            
        Returns:
            True if installation successful, False otherwise
        """
        self.start_time = time.time()
        self.error_message = None
        self.stopped_services = []
        
        try:
            logger.info(f"Starting installation of package: {package_path}")
            
            # Phase 1: Pre-installation checks and validation
            if not await self._pre_install_checks(package_path):
                return False
            
            if not await self._extract_and_validate_package(package_path):
                return False
            
            # Phase 2: Create backup
            if not await self._create_system_backup():
                return False
            
            # Phase 3: Stop services
            if not await self._stop_services():
                await self._rollback_installation()
                return False
            
            # Phase 4: Install files
            if not await self._install_files():
                await self._rollback_installation()
                return False
            
            # Phase 5: Configure system
            if not await self._configure_system():
                await self._rollback_installation()
                return False
            
            # Phase 6: Start services
            if not await self._start_services():
                await self._rollback_installation()
                return False
            
            # Phase 7: Validate installation
            if not await self._validate_installation():
                await self._rollback_installation()
                return False
            
            # Success!
            await self._update_progress(
                InstallState.COMPLETE, "COMPLETE",
                "Installation completed successfully", 100
            )
            
            logger.info("Installation completed successfully")
            
            # Start post-install monitoring if enabled
            if self.enable_auto_rollback:
                await self.start_post_install_monitoring()
            
            # Cleanup old backups
            self.backup_manager.cleanup_old_backups()
            
            return True
            
        except Exception as e:
            self.error_message = str(e)
            logger.error(f"Installation failed: {e}")
            
            # Attempt rollback
            await self._rollback_installation()
            return False
            
        finally:
            # Always cleanup temporary files
            self._cleanup_temp_files()
    
    def get_install_progress(self) -> InstallProgress:
        """Get current installation progress."""
        elapsed = time.time() - self.start_time if self.start_time else 0
        
        return InstallProgress(
            state=self.current_state,
            stage=self.current_state.value,
            progress_percent=self.current_progress,
            current_step=self.current_step,
            total_steps=self.total_steps,
            elapsed_seconds=elapsed,
            estimated_time_seconds=None,
            error_message=self.error_message
        )
    
    def set_rollback_callback(self, callback: Callable[[RollbackTrigger, str], None]):
        """Set callback for rollback events.
        
        Args:
            callback: Function to call when rollback is triggered
        """
        self.rollback_callback = callback
    
    def initialize_failure_detector(self, config_path: Optional[str] = None):
        """Initialize failure detector for post-install monitoring.
        
        Args:
            config_path: Path to failure detection configuration
        """
        if not self.failure_detector:
            self.failure_detector = FailureDetector(config_path)
            self.failure_detector.set_failure_callback(self._on_failure_detected)
    
    async def _on_failure_detected(self, failure_event: FailureEvent, 
                                  diagnostic_report: DiagnosticReport):
        """Handle failure detection callback.
        
        Args:
            failure_event: Detected failure event
            diagnostic_report: Comprehensive diagnostic report
        """
        logger.error(f"Failure detected during monitoring: {failure_event.message}")
        
        if not self.enable_auto_rollback:
            logger.info("Auto-rollback disabled, manual intervention required")
            return
        
        # Determine rollback trigger based on failure type
        trigger_mapping = {
            "SERVICE_FAILURE": RollbackTrigger.AUTO_SERVICE_FAILURE,
            "RESOURCE_EXHAUSTION": RollbackTrigger.AUTO_RESOURCE_EXHAUSTION,
            "CONNECTIVITY_LOSS": RollbackTrigger.AUTO_CONNECTIVITY_LOSS,
            "FILE_CORRUPTION": RollbackTrigger.AUTO_FILE_CORRUPTION,
            "STARTUP_FAILURE": RollbackTrigger.AUTO_SERVICE_FAILURE,
        }
        
        trigger = trigger_mapping.get(
            failure_event.failure_type.value, 
            RollbackTrigger.AUTO_HEALTH_CHECK_FAILURE
        )
        
        # Check if automatic rollback is recommended
        if diagnostic_report.rollback_recommendation == "automatic":
            logger.info(f"Initiating automatic rollback due to: {failure_event.message}")
            await self._trigger_rollback(trigger, failure_event.message)
        elif diagnostic_report.rollback_recommendation == "recommended":
            logger.warning(f"Rollback recommended but not automatic: {failure_event.message}")
            # Could send notification or wait for manual intervention
    
    async def _trigger_rollback(self, trigger: RollbackTrigger, reason: str):
        """Trigger rollback operation.
        
        Args:
            trigger: Reason for rollback trigger
            reason: Human-readable reason description
        """
        try:
            self.last_rollback_trigger = trigger
            
            # Stop monitoring during rollback
            await self._stop_post_install_monitoring()
            
            # Record rollback event
            rollback_event = {
                'timestamp': datetime.now().isoformat(),
                'trigger': trigger.value,
                'reason': reason,
                'package_version': self.current_package.version if self.current_package else 'unknown',
                'backup_path': self.backup_path
            }
            self.rollback_history.append(rollback_event)
            
            # Call rollback callback
            if self.rollback_callback:
                try:
                    await self.rollback_callback(trigger, reason)
                except Exception as e:
                    logger.error(f"Error in rollback callback: {e}")
            
            # Execute rollback
            logger.info(f"Starting rollback: {trigger.value}")
            success = await self._rollback_installation()
            
            if success:
                rollback_event['result'] = 'success'
                rollback_event['completed_at'] = datetime.now().isoformat()
                logger.info("Automatic rollback completed successfully")
            else:
                rollback_event['result'] = 'failed'
                rollback_event['error'] = self.error_message
                logger.error("Automatic rollback failed")
            
        except Exception as e:
            logger.error(f"Error during rollback trigger: {e}")
    
    async def manual_rollback(self, reason: str = "Manual rollback requested") -> bool:
        """Trigger manual rollback.
        
        Args:
            reason: Reason for manual rollback
            
        Returns:
            True if rollback successful, False otherwise
        """
        logger.info(f"Manual rollback requested: {reason}")
        
        if not self.backup_path:
            logger.error("No backup available for rollback")
            return False
        
        try:
            await self._trigger_rollback(RollbackTrigger.MANUAL, reason)
            return self.current_state == InstallState.RESTORED
        except Exception as e:
            logger.error(f"Manual rollback failed: {e}")
            return False
    
    async def start_post_install_monitoring(self):
        """Start post-installation monitoring for automatic rollback."""
        if not self.enable_auto_rollback:
            logger.info("Auto-rollback disabled, skipping post-install monitoring")
            return
        
        if not self.failure_detector:
            self.initialize_failure_detector()
        
        logger.info(f"Starting post-install monitoring for {self.post_install_monitoring_duration} seconds")
        
        # Update state
        await self._update_progress(
            InstallState.MONITORING, "MONITORING",
            "Monitoring system stability", 100
        )
        
        # Start failure detector
        await self.failure_detector.start_monitoring()
        
        # Schedule monitoring stop
        self._monitoring_task = asyncio.create_task(
            self._stop_monitoring_after_duration()
        )
    
    async def _stop_monitoring_after_duration(self):
        """Stop monitoring after specified duration."""
        try:
            await asyncio.sleep(self.post_install_monitoring_duration)
            await self._stop_post_install_monitoring()
            logger.info("Post-install monitoring completed - system stable")
        except asyncio.CancelledError:
            logger.info("Post-install monitoring cancelled")
    
    async def _stop_post_install_monitoring(self):
        """Stop post-installation monitoring."""
        if self.failure_detector:
            await self.failure_detector.stop_monitoring()
        
        if self._monitoring_task:
            self._monitoring_task.cancel()
            try:
                await self._monitoring_task
            except asyncio.CancelledError:
                pass
            self._monitoring_task = None
    
    def get_rollback_history(self, limit: Optional[int] = None) -> List[Dict]:
        """Get rollback history.
        
        Args:
            limit: Maximum number of entries to return
            
        Returns:
            List of rollback events
        """
        history = self.rollback_history
        if limit:
            history = history[-limit:]
        return history
    
    def get_available_backups(self) -> List[Dict]:
        """Get list of available backup versions.
        
        Returns:
            List of backup information
        """
        try:
            backup_dirs = [d for d in self.backup_manager.backup_dir.iterdir() 
                          if d.is_dir() and d.name.startswith("backup_")]
            
            backups = []
            for backup_dir in backup_dirs:
                try:
                    # Parse backup directory name for version info
                    parts = backup_dir.name.split('_')
                    if len(parts) >= 3:
                        version = parts[1]
                        timestamp_str = '_'.join(parts[2:])
                        
                        # Get backup size
                        total_size = sum(
                            f.stat().st_size for f in backup_dir.rglob('*') 
                            if f.is_file()
                        )
                        
                        backups.append({
                            'version': version,
                            'timestamp': timestamp_str,
                            'path': str(backup_dir),
                            'size_mb': round(total_size / (1024 ** 2), 2),
                            'created_at': datetime.fromtimestamp(
                                backup_dir.stat().st_mtime
                            ).isoformat()
                        })
                except Exception as e:
                    logger.warning(f"Failed to parse backup info for {backup_dir}: {e}")
            
            # Sort by creation time (newest first)
            backups.sort(key=lambda x: x['created_at'], reverse=True)
            return backups
            
        except Exception as e:
            logger.error(f"Failed to get available backups: {e}")
            return []
    
    async def rollback_to_backup(self, backup_path: str, reason: str = "Manual rollback to specific backup") -> bool:
        """Rollback to a specific backup.
        
        Args:
            backup_path: Path to backup directory
            reason: Reason for rollback
            
        Returns:
            True if rollback successful, False otherwise
        """
        if not Path(backup_path).exists():
            logger.error(f"Backup path does not exist: {backup_path}")
            return False
        
        # Temporarily set backup path
        original_backup_path = self.backup_path
        self.backup_path = backup_path
        
        try:
            await self._trigger_rollback(RollbackTrigger.MANUAL, reason)
            return self.current_state == InstallState.RESTORED
        except Exception as e:
            logger.error(f"Rollback to specific backup failed: {e}")
            return False
        finally:
            # Restore original backup path
            if original_backup_path:
                self.backup_path = original_backup_path
    
    def is_rollback_available(self) -> bool:
        """Check if rollback is possible.
        
        Returns:
            True if rollback is available, False otherwise
        """
        return self.backup_path is not None and Path(self.backup_path).exists()
    
    async def health_check(self) -> Dict[str, Any]:
        """Perform immediate health check.
        
        Returns:
            Current system health status
        """
        if not self.failure_detector:
            self.initialize_failure_detector()
        
        return await self.failure_detector.manual_health_check()